# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.

include	<ctype.h>
include	<chars.h>
include	<pattern.h>
include	<syserr.h>
include	<diropen.h>

.help fntgfn
.nf _________________________________________________________________________
File Name Template Package

This package contains routines to expand a file name template string into a
list of file names, and to access the individual elements of the list.  The
template is a list of file names, patterns, and/or list file names.  The
concatenation operator may be used within input list elements to form new
output filenames.  String substitution may also be used to form new filenames.

Sample template string:

	alpha, *.x, data* // .pix, [a-m]*, @list_file

This template would be expanded as the file "alpha", followed in successive
calls by all the files in the current directory whose names end in ".x",
followed by all files whose names begin with "data" with the extension ".pix"
appended, and so on.  The @ character signifies a list file (file containing
regular file names).

String substitution uses the first string given for the template, expands
the template, and for each filename generated by the template, substitutes
the second string to generate a new filename.  Some examples follow.

	*.%x%y%			change extension to `y'
	*%%_abc%.imh		append `_abc' to root
	nite%1%2%.1024.imh	change `nite1' to `nite2'

Main entry points:

	fntopnb - expand template and open a buffered filename list
	fntgfnb - get next filename from buffered list (sequential)
	fntrfnb - get next filename from buffered list (random)
	fntclsb - close buffered list
	fntlenb - get number of filenames in a buffered list
	fntrewb - rewind the list

Low Level Entry Points:

	 fntopn - open an unbuffered filename list
	 fntgfn - get next filename from unbuffered list
	 fntcls - close unbuffered list

The B suffix routines are the highest level and most convenient to use.
The remaining routines expand a template "on the fly" and do not permit
sorting or determination of the length of the list.
.endhelp ____________________________________________________________________

# FNTB descriptor structure.
define	LEN_FNTBHDR		5
define	FNTB_MAGIC		5164
define	B_MAGIC			Memi[$1]
define	B_SBUFPTR		Memi[$1+1]	# string buffer pointer
define	B_NSTR			Memi[$1+2]	# number of strings
define	B_STRNUM		Memi[$1+3]	# used to read list
define	B_STRINDX		Memi[$1+$2-1+4]	# index of string

# FNTU descriptor structure.
define	LEN_FNTUHDR		(10+1024+256)
define	FNTU_MAGIC		5664
define	U_MAGIC			Memi[$1]
define	U_FILDES		Memi[$1+1]
define	U_TEMPLATE		Memi[$1+2]	# pointer
define	U_TEMPLATE_INDEX	Memi[$1+3]
define	U_PATTERN		(P2C($1+10))
define	U_LDIR			(P2C($1+1034))

# Special characters and size limiting definitions.
define	TOK_DELIM		','		# token delimiter
define	LIST_FILE_CHAR		'@'		# @listfile
define	CH_EDIT			'%'		# string substitution metachar
define	SZ_PATTERN		1023
define	SZ_LDIR			255
define	SZ_PATSTR		1023
define	MAX_EDIT		8
define	MAX_PATTERNS		8

# Tokens.
define	EO_TEMPLATE		1
define	LIST_FILE		2
define	PATTERN_STRING		3
define	FILE_NAME		4

# Size limiting definitions (initial buffer sizes).
define	SZ_DEFSTRBUF		2048		# default string buffer size
define	LEN_INDEXVECTOR		256		# initial length of index vector


# FNTOPNB -- General open buffered list routine, for any type of filename list.
# Expand template into string buffer, sort if so indicated.

int procedure fntopnb (template, sort)

char	template[ARB]		# filename template
int	sort			# sort expanded patterns

int	nedit[MAX_PATTERNS], junk, nchars
bool	is_template[MAX_PATTERNS], is_edit[MAX_PATTERNS], sortlist, is_url
pointer	sp, pbuf, fname, rname, extn, ebuf, sbuf, list, ip, op, ep, pp
pointer	patp[MAX_PATTERNS], flist[MAX_PATTERNS], editp[MAX_EDIT]
int	nlists, npat, nstr, maxstr, nextch, sz_sbuf, ix, first_string, ch, i
int	fntopn(), fntgfn(), fnt_getpat(), gstrcpy(), fnt_edit(), stridx()
int	patmake(), patmatch(), strncmp()
errchk	fntopn, fntgfn, syserr, malloc, realloc

begin
	call smark (sp)
	call salloc (rname, SZ_FNAME, TY_CHAR)
	call salloc (fname, SZ_FNAME, TY_CHAR)
	call salloc (extn,  SZ_FNAME, TY_CHAR)
	call salloc (pbuf,  SZ_LINE, TY_CHAR)
	call salloc (ebuf,  SZ_LINE, TY_CHAR)

	# Allocate list descriptor.
	call malloc (list, LEN_FNTBHDR + LEN_INDEXVECTOR, TY_INT)
	call malloc (sbuf, SZ_DEFSTRBUF, TY_CHAR)

	B_MAGIC(list) = FNTB_MAGIC
	maxstr = LEN_INDEXVECTOR
	sz_sbuf = SZ_DEFSTRBUF
	nextch = 1				# offset into string buffer
	nstr = 0

	# Read the file names into the string buffer.  Dynamically adjust
	# the size of the string buffer and/or index vector as necessary.
	# There must always be at least SZ_FNAME chars left in the string
	# buffer.  The outer loop is over comma delimited fields of the
	# filename template.  The inner loop is over individual filenames.

	ix = 1
	while (fnt_getpat (template, ix, patp, npat, pbuf, SZ_LINE) > 0) {
	    first_string = nstr + 1
	    sortlist = (sort == YES)
	    nlists = 0
	    ep = ebuf

	    # Each piece of the current comma delimited template may consist
	    # of several sublists to be independently expanded and concatenated
	    # to form each output filename.  The lists must either be degenerate
	    # (a simple string) or actual lists to be expanded with FNTOPN.

	    do i = 1, npat {
		is_template[i] = false
		is_edit[i] = false
		nedit[i] = 0
		op = patp[i]

		# Examine sublist to see if it is a template or a string
		# constant.  If template, open file list.  Template
		# metacharacters may be escaped to be included in filenames.
		# If the pattern contains edit substitution sequences it
		# must be processed to remove the substitution strings.

		is_url = false
		for (ip=op;  Memc[ip] != EOS;  ip=ip+1) {
		    ch = Memc[ip]

		    if (ch == ':' && strncmp (Memc[ip+1], "//", 2) == 0) {
			# URL string.
			is_template[i] = false
			is_edit[i] = false
			is_url = true
		    } else if (!is_url && stridx (Memc[ip], "@*?[%") > 0) {
			if (ip > patp[i] && Memc[ip-1] == '\\') {
			    Memc[op-1] = ch
			    ip = ip + 1
			    ch = Memc[ip]
			} else if (ch == CH_EDIT) {
			    is_edit[i] = true
			} else {
			    if (ch == '@' && op == ip)
				sortlist = false
			    if (!is_url)
			        is_template[i] = true
			}
		    }

		    Memc[op] = ch
		    op = op + 1
		}

		Memc[op] = EOS

		# Open filename template if pattern contained metacharacters.
		# A string constant containing edit string substitution is a
		# special case, eg. "file%%_2%.ext".

		if (is_template[i] || is_edit[i]) {
		    editp[i] = ep
		    call fnt_mkpat (Memc[patp[i]], Memc[fname], SZ_FNAME,
			ep, nedit[i])
		    flist[i] = fntopn (Memc[fname])

		    # In the case of a string constant edit we do not really
		    # have a file template, but we open one anyhow just to
		    # make use of the common code and the descriptor.

		    if (!is_template[i]) {
			# Encode the pattern (containing the %%).
			junk = patmake (Memc[fname], Memc[U_PATTERN(flist[i])],
			    SZ_PATTERN)

			# Strip the %% from the pattern, leaving the "input"
			# filename in patp[i].

			op = patp[i]
			for (ip=fname;  Memc[ip] != EOS;  ip=ip+1)
			    if (Memc[ip] != CH_EDIT) {
				Memc[op] = Memc[ip]
				op = op + 1
			    }
			Memc[op] = EOS

			# Now match the stripped pattern against the %%
			# pattern.  This sets up U_PATTERN for the edit.

			junk = patmatch (Memc[patp[i]],
			    Memc[U_PATTERN(flist[i])])
		    } else
			nlists = nlists + 1
		}
	    }

	    # Expand the template into a sequence of filenames in the string
	    # buffer, saving the indices of the list elements in the STRINDX
	    # array.  Reallocate a larger buffer if necessary.  If the sublists
	    # are not all the same length the shortest list will terminate the
	    # output list.

	    repeat {
		# Concatenate the next element from each sublist; the sublists
		# may be either real lists or string constants.  Concatenate
		# only to the root filename.

		Memc[extn] = EOS
		op = fname

		do i = 1, npat {
		    # Save first extension field encountered and set op to
		    # end of root.

		    if (Memc[extn] == EOS)
			for (ip=op-1;  ip > fname;  ip=ip-1)
			    if (Memc[ip] == '.') {
				call strcpy (Memc[ip], Memc[extn], SZ_FNAME)
				op = ip
				break
			    }

		    # Concatenate the next file element.  This can be either a
		    # file name from a file template, a constant file name from
		    # a string edit expression, or a simple string constant.

		    if (!is_url && (is_template[i] || is_edit[i])) {
			ip = rname
			pp = flist[i]
			if (is_template[i]) {
			    if (fntgfn (pp, Memc[rname], SZ_FNAME) == EOF) {
				op = fname
				break

			    } else if (U_FILDES(pp) != NULL) {
				# Reading from a directory or list; set offset
				# of substring to be edited to exclude any
				# ldir prefix, since this will not have been
				# used for the pattern match.

				nchars = gstrcpy (Memc[U_LDIR(pp)],Memc[op],ARB)
				op = op + nchars
				ip = ip + nchars
			    }
			} else
			    call strcpy (Memc[patp[i]], Memc[rname], SZ_FNAME)

			op = op + fnt_edit (Memc[ip], Memc[op], editp[i],
			    nedit[i], Memc[U_PATTERN(pp)])

		    } else {
			op = op + gstrcpy (Memc[patp[i]], Memc[op], ARB)
		    }
		}

		# End of list if nothing returned.
		if (op == fname)
		    break

		# Tack extension back on.
		if (Memc[extn] != EOS)
		    op = op + gstrcpy (Memc[extn], Memc[op], ARB)

		# Need more room for list element pointers?
		nstr = nstr + 1
		if (nstr > maxstr) {
		    maxstr = maxstr + LEN_INDEXVECTOR
		    call realloc (list, LEN_FNTBHDR + maxstr, TY_INT)
		}

		# Out of space in string buffer?
		if (nextch + (op - fname) >= sz_sbuf) {
		    sz_sbuf = sz_sbuf + SZ_DEFSTRBUF
		    call realloc (sbuf, sz_sbuf, TY_CHAR)
		}

		# Save index of list element, move chars to string buffer.
		# Allow space for the EOS after each string.

		B_STRINDX(list,nstr) = nextch
		nextch = nextch +
		    gstrcpy (Memc[fname], Memc[sbuf+nextch-1], ARB) + 1

	    } until (nlists == 0)

	    do i = 1, npat
		if (is_template[i] || is_edit[i])
		    call fntcls (flist[i])

	    # If sorting is desired and the pattern did not specify an explicit
	    # list (e.g., "@listfile"), sort the last batch of filenames.

	    if (sortlist && nstr > first_string)
		call strsrt (B_STRINDX(list,first_string), Memc[sbuf],
		    nstr - first_string + 1)
	}

	# Update the string buffer descriptor, return unused buffer space.
	# Rewind the list in preparation for reading (set strnum=1).

	call realloc (sbuf, nextch, TY_CHAR)
	call realloc (list, LEN_FNTBHDR + nstr, TY_INT)

	B_NSTR(list)	= nstr
	B_STRNUM(list)	= 1
	B_SBUFPTR(list)	= sbuf

	call sfree (sp)
	return (list)
end


# FNT_MKPAT -- Take a pattern string possibly containing %a%b% string
# substitution sequences, returning a pattern string as required for PATMAKE,
# and a sequence of substitution strings for later use by FNT_EDIT to edit
# filenames matched by FNTGFN.

procedure fnt_mkpat (pat, patstr, maxch, ep, nedit)

char	pat[ARB]		# pattern with embedded substitution sequences
char	patstr[maxch]		# receives pattern as req'd by PATMAKE
int	maxch
pointer	ep			# where to put substitution string chars
int	nedit			# number of substitution chars

int	nhat
int	ip, op

begin
	nedit = 0
	nhat  = 0
	op    = 1

	for (ip=1;  pat[ip] != EOS;  ip=ip+1) {
	    if (pat[ip] == CH_EDIT) {
		if (ip > 1 && pat[ip-1] == '\\') {
		    # Moved escaped metacharacter to pattern string.
		    patstr[op] = pat[ip]
		    op = op + 1

		} else if (nhat > 0) {
		    # Copy substitution string to ebuf.
		    patstr[op] = pat[ip]
		    op = op + 1
		    nedit = nedit + 1

		    ip = ip + 1
		    while (pat[ip] != EOS && pat[ip] != CH_EDIT) {
			Memc[ep] = pat[ip]
			ep = ep + 1
			ip = ip + 1
		    }

		    Memc[ep] = EOS
		    ep = ep + 1
		    if (pat[ip] == EOS)
			ip = ip - 1
		    nhat = 0

		} else {
		    patstr[op] = pat[ip]
		    op = op + 1
		    nhat = nhat + 1
		}

	    } else {
		patstr[op] = pat[ip]
		op = op + 1
		if (op > maxch)
		    break
	    }
	}

	patstr[op] = EOS
end


# FNT_EDIT -- Perform string substitution on a matched filename, using the
# list of substitution strings written by FNT_MKPAT, the first of which is
# pointed to by EDITP.  The regions to be replaced were marked symbolically
# by the CH_EDIT characters in the user supplied pattern.  The actual indices
# of these regions depend upon the actual filename and are saved by the
# pattern matching code in the encoded pattern buffer PATBUF, for retrieval
# by PATINDEX.  Carry out the substitution and return the length of the
# output string as the function argument.

int procedure fnt_edit (in, out, editp, nedit, patbuf)

char	in[ARB]			# input string to be edited
char	out[ARB]		# receives edited string
pointer	editp			# pointer to first substitution string
int	nedit			# number of edits required
char	patbuf[ARB]		# encoded pattern

pointer	ep
int	ip1, ip2, ip, op, i
int	patindex()

begin
	ep = editp - 1
	ip = 1
	op = 1

	do i = 1, nedit {
	    # Get indices of first and last+1 characters to be substituted for
	    # in the input string.

	    ip1 = patindex (patbuf, (i-1) * 2 + 1)
	    ip2 = patindex (patbuf, (i-1) * 2 + 2)
	    if (ip1 == 0 || ip2 == 0 || ip1 > ip2)
		break		# cannot happen

	    # Copy up to first char to be replaced.
	    for (;  ip < ip1;  ip=ip+1) {
		out[op] = in[ip]
		op = op + 1
	    }

	    # Append substitution string.
	    for (ep=ep+1;  Memc[ep] != EOS;  ep=ep+1) {
		out[op] = Memc[ep]
		op = op + 1
	    }

	    # Continue at character IP2 in the input string.
	    ip = ip2
	}

	# Copy remainder of input string to the output string.
	for (;  in[ip] != EOS;  ip=ip+1) {
	    out[op] = in[ip]
	    op = op + 1
	}

	out[op] = EOS
	return (op - 1)
end


# FNTGFNB -- Return the next filename from the list.

int procedure fntgfnb (list, fname, maxch)

pointer	list			# list descriptor pointer
char	fname[ARB]		# output filename
int	maxch

pointer	strptr
int	file_number
int	gstrcpy()
errchk	syserr

begin
	if (B_MAGIC(list) != FNTB_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	file_number = B_STRNUM(list)
	if (file_number > B_NSTR(list))
	    return (EOF)
	else {
	    B_STRNUM(list) = file_number + 1
	    strptr = B_SBUFPTR(list) + B_STRINDX(list,file_number) - 1
	    return (gstrcpy (Memc[strptr], fname, maxch))
	}
end


# FNTRFNB -- Return the indexed filename from the list.  For applications
# which need to access the list at random.  Returns len(fname) or EOF for
# references to nonexistent list elements.

int procedure fntrfnb (list, index, fname, maxch)

pointer	list			# list descriptor pointer
int	index			# index of list element to be returned
char	fname[ARB]		# output filename
int	maxch

pointer	strptr
int	gstrcpy()
errchk	syserr

begin
	if (B_MAGIC(list) != FNTB_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	if (index < 1 || index > B_NSTR(list))
	    return (EOF)
	else {
	    strptr = B_SBUFPTR(list) + B_STRINDX(list,index) - 1
	    return (gstrcpy (Memc[strptr], fname, maxch))
	}
end


# FNTCLSB -- Close a buffered list and return all storage.

procedure fntclsb (list)

pointer	list			# list descriptor pointer
errchk	syserr

begin
	if (B_MAGIC(list) != FNTB_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	call mfree (B_SBUFPTR(list), TY_CHAR)
	call mfree (list, TY_INT)
end


# FNTREWB -- Rewind a buffered filename list.

procedure fntrewb (list)

pointer	list			# list descriptor pointer
errchk	syserr

begin
	if (B_MAGIC(list) != FNTB_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	B_STRNUM(list) = 1
end


# FNTLENB -- Return the number of filenames in the list.

int procedure fntlenb (list)

pointer	list			# list descriptor pointer
errchk	syserr

begin
	if (B_MAGIC(list) != FNTB_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	return (B_NSTR(list))
end


# FNT_GETPAT -- Return the next comma delimited field from the template string
# with any leading or trailing whitespace stripped off.  The field may consist
# of a simple string constant, a filename template, or a sequence of either
# delimited by concatenation operators //.  We do not make any distinction here
# between string constants and patterns; return the \ with all escape sequences
# as this will be stripped by the higher level code if used to include pattern
# matching metacharacters in filenames.

int procedure fnt_getpat (template, ix, patp, npat, sbuf, maxch)

char	template[ARB]		# template from which to extract field
int	ix			# next char in template
pointer	patp[MAX_PATTERNS]	# receives pointers to sublists (patterns)
int	npat			# receives number of PATP elements set
pointer	sbuf			# used to store output strings
int	maxch			# maxch chars out

int	ch, peek
bool	is_url
pointer	op

int	strncmp(), stridx()
errchk	syserr

begin
	while (IS_WHITE(template[ix]) || template[ix] == ',')
	    ix = ix + 1

	patp[1] = sbuf
	npat = 1
	op = sbuf
	is_url = false

	#for (ch=template[ix];  ch != EOS && ch != ',';  ch=template[ix]) {
	for (ch=template[ix];  ch != EOS;  ch=template[ix]) {
	    peek = template[ix+1]
	    if (IS_WHITE (ch)) {
		# Ignore all whitespace.
		ix = ix + 1
		next

	    } else if ((is_url && ch == ',')) {
		if (stridx (peek, "+-.0123456789") == 0) {
		    break
		} else {
		    # Keep a comma in a URL followed by a digit
		    Memc[op] = ','
		    op = op + 1
		    ix = ix + 1
		}

	    } else if (!is_url && ch == ',') {
		break

	    } else if (ch == '\\' && template[ix+1] == ',') {
		# Escape a comma.
		Memc[op] = ','
		op = op + 1
		ix = ix + 2

	    } else if (!is_url && (ch == '/' && template[ix+1] == '/')) {
		# Concatenation operator: start a new sublist.
		Memc[op] = EOS
		op = op + 1
		ix = ix + 2
		npat = npat + 1
		if (npat > MAX_PATTERNS)
		    call syserr (SYS_FNTMAXPAT)
		patp[npat] = op

	    } else if (ch == ':' && strncmp ("//", template[ix+1], 2) == 0) {
		# Start of URL string, deposit in output list.
		Memc[op] = ch
		op = op + 1
		ix = ix + 1
		is_url = true

	    } else {
		# Ordinary character, deposit in output list.
		Memc[op] = ch
		op = op + 1
		ix = ix + 1
	    }

	    if (op - sbuf > maxch)
		break
	}

	Memc[op] = EOS
	return (op - sbuf)
end


# FNTGFN -- Get the next file name from the named parameter (template).
# This is the guy that does all the work.  A file name may be selected from
# a directory file or list file by pattern matching, or may come from the
# template list string itself.

int procedure fntgfn (pp, outstr, maxch)

pointer	pp			# pattern pointer
char	outstr[ARB]		# output filename
int	maxch

bool	match
pointer	ip, sp, linebuf, fname, patstr
int	nchars, token, first_ch, last_ch, status

bool	streq()
int	getline(), gpatmatch(), patmake(), nowhite(), gstrcat()
int	fnt_read_template(), fnt_open_list()
errchk	salloc, getline, close, fnt_open_list, syserr

begin
	if (pp == NULL || U_MAGIC(pp) != FNTU_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	call smark (sp)					# get buffers
	call salloc (linebuf, SZ_LINE, TY_CHAR)
	call salloc (patstr, SZ_PATSTR, TY_CHAR)
	call salloc (fname, SZ_PATHNAME, TY_CHAR)

	repeat {
	    # Read file names from either list file or directory file, until
	    # one is found which matches pattern, or until EOF is reached.
	    # Make sure pattern matches the ENTIRE file name string, rather
	    # than a substring.

	    if (U_FILDES(pp) != NULL) {			# reading from a file?
		while (getline (U_FILDES(pp), Memc[linebuf]) != EOF) {
		    for (ip=linebuf;  IS_WHITE (Memc[ip]);  ip=ip+1)
			;
		    nchars = nowhite (Memc[ip], Memc[fname], maxch)
		    if (nchars == 0)			# skip blank lines
			next

		    # If the encoded pattern is the null string match anything.
		    if (Memc[U_PATTERN(pp)] == EOS) {
			match = true
		    } else if (gpatmatch (Memc[fname], Memc[U_PATTERN(pp)],
			first_ch, last_ch) > 0) {
			match = (first_ch == 1 && last_ch == nchars)
		    } else
			match = false

		    if (match) {
			call strcpy (Memc[U_LDIR(pp)], outstr, maxch)
			nchars = gstrcat (Memc[fname], outstr, maxch)
			call sfree (sp)
			return (nchars)
		    }
		}

		call close (U_FILDES(pp))
		U_FILDES(pp) = NULL
	    }

	    switch (fnt_read_template (pp, Memc[linebuf], SZ_LINE, token)) {
	    case EO_TEMPLATE:
		nchars = EOF
		outstr[1] = EOS
		call sfree (sp)
		return (nchars)

	    case LIST_FILE, PATTERN_STRING:
		# Break the pattern string into a list file or directory
		# name and a pattern.

		if (token == PATTERN_STRING) {
		    Memc[patstr] = '^'
		    ip = patstr + 1
		} else
		    ip = patstr

		U_FILDES(pp) = fnt_open_list (Memc[linebuf], Memc[ip],
		    SZ_PATSTR-1, Memc[fname], Memc[U_LDIR(pp)], token)

		# Encode the pattern.  If the pattern is matchall set encoded
		# pattern string to NULL and pattern matching will be skipped.

		if (streq (Memc[patstr], "?*"))
		    Memc[U_PATTERN(pp)] = EOS
		else {
		    status = patmake (Memc[patstr], Memc[U_PATTERN(pp)],
			SZ_PATTERN)
		    if (status == ERR)
			call syserr (SYS_FNTBADPAT)
		}
		
	    default:					# simple file name
		nchars = nowhite (Memc[linebuf], outstr, maxch)
		if (nchars > 0) {
		    call sfree (sp)
		    return (nchars)
		}
	    }
	}
end


# FNT_READ_TEMPLATE -- Get next token from template string, return integer
# code identifying the type of token.

int procedure fnt_read_template (pp, outstr, maxch, token)

pointer	pp				#I pointer to param descriptor
char	outstr[maxch]			#O receives token
int	maxch				#I max chars out
int	token				#O token type code

int	nseen, i
pointer	ip, ip_start, op, cp
int	stridx(), strncmp()

begin
	ip = U_TEMPLATE_INDEX(pp)			# retrieve pointer
	while (IS_WHITE (Memc[ip]))
	    ip = ip + 1


	switch (Memc[ip]) {
	case EOS:
	    op = 1
	    token = EO_TEMPLATE

	case LIST_FILE_CHAR:				# list file spec
	    ip = ip + 1					# skip the @
	    for (op=1;  Memc[ip] != TOK_DELIM && Memc[ip] != EOS;  op=op+1) {
		outstr[op] = Memc[ip]
		ip = ip + 1
	    }
	    token = LIST_FILE
	    if (Memc[ip] == TOK_DELIM)
		ip = ip + 1

	default:					# fname or pat string
	    token = FILE_NAME
	    # Extract token.  Determine if regular file name or pattern string.
	    # Disable metacharacters not useful for file name patterns.

	    ip_start = ip
	    for (op=1;  Memc[ip] != EOS;  ip=ip+1) {
		if (Memc[ip] == CH_ESCAPE && Memc[ip+1] != EOS) {
		    # Escape sequence.  Pass both the escape and the escaped
		    # character on to the lower level code.

		    outstr[op] = CH_ESCAPE
		    op = op + 1
		    ip = ip + 1

		} else if (Memc[ip] == TOK_DELIM) {
		    ip = ip + 1
		    break

		} else if (Memc[ip] == FNLDIR_CHAR || Memc[ip] == '/') {
		    token = FILE_NAME

		} else if (Memc[ip] == '*') {
		    # Map "*" into "?*".
		    token = PATTERN_STRING
		    outstr[op] = '?'
		    op = op + 1

		} else if (Memc[ip] == '%') {
		    # The % metacharacter must appear twice (not three times,
		    # as the high level code strips the subsitution field) to
		    # be recognized as the pattern substitution metacharacter.

		    nseen = 0
		    do i = 1, ARB {
			cp = ip_start + i - 1
			if (Memc[cp] == EOS || Memc[cp] == TOK_DELIM)
			    break
			else if (Memc[cp] == '%' && Memc[cp-1] != '\\')
			    nseen = nseen + 1
		    }
		    if (nseen < 2) {
			outstr[op] = CH_ESCAPE
			op = op + 1
		    }
		} else if (stridx (Memc[ip], "[?{") > 0)
		    token = PATTERN_STRING

		outstr[op] = Memc[ip]
		op = op + 1
	    }
	}
	    
	# Remove any trailing whitespace.
	op = op - 1
	while (op > 0 && IS_WHITE (outstr[op]))
	    op = op - 1
	outstr[op+1] = EOS

	if (op > 0)
	    if (outstr[op] == FNLDIR_CHAR || outstr[op] == '/')
		token = PATTERN_STRING

	U_TEMPLATE_INDEX(pp) = ip			# update pointer

	return (token)
end


# FNT_OPEN_LIST -- Open list file or directory.  If reading from a directory,
# open the current directory if a directory name is not given.  Extract
# pattern string (if any), and return in PATSTR.  If no pattern string is
# given, return a pattern which will match all files in the list.

int procedure fnt_open_list (str, patstr, maxch, fname, ldir, ftype)

int	maxch, ftype
char	ldir[SZ_LDIR]
char	str[ARB], patstr[maxch], fname[SZ_FNAME]
int	fd, ip, op, fnt_delim, pat_start, dirmode
int	open(), diropen()
errchk	open, diropen, fpathname

begin
	op = 1
	fnt_delim = NULL
	pat_start = NULL

	# Search for a valid directory prefix.
	for (ip=1;  str[ip] != EOS;  ip=ip+1) {
	    fname[op] = str[ip]
	    if (ftype != LIST_FILE)
		if (fname[op] == FNLDIR_CHAR || fname[op] == '//')
		    if (op == 1 || fname[op-1] != '\\') {
			fnt_delim = op
			pat_start = ip + 1
		    }
	    op = op + 1
	}
	fname[op] = EOS

	if (ftype == LIST_FILE) {
	    if (fnt_delim != NULL)
		fname[fnt_delim] = EOS
	    fd = open (fname, READ_ONLY, TEXT_FILE)
	    ldir[1] = EOS

	} else {
	    if (fnt_delim != NULL)		# specific directory
		fname[fnt_delim+1] = EOS
	    else				# current directory
		fname[1] = EOS
	    call fpathname (fname, ldir, SZ_LDIR)

	    dirmode = SKIP_HIDDEN_FILES
	    if (pat_start != NULL) {
		if (str[pat_start] == '.')
		    dirmode = PASS_HIDDEN_FILES
	    } else if (ftype != LIST_FILE && str[1] == '.')
		dirmode = PASS_HIDDEN_FILES

	    fd = diropen (ldir, dirmode)
	    call strcpy (fname, ldir, SZ_LDIR)
	}

	# If pattern string is appended to list file name, extract
	# it, otherwise set the default pattern "match all" (*).

	op = 1
	if (pat_start != NULL)
	    ip = pat_start
	else if (ftype != LIST_FILE)
	    ip = 1

	for (;  str[ip] != EOS;  ip=ip+1) {
	    patstr[op] = str[ip]
	    op = op + 1
	}

	# No pattern string given, default to "?*".
	if (op == 1) {
	    patstr[1] = CH_ANY
	    patstr[2] = CH_CLOSURE
	    op = 3
	}
	patstr[op] = EOS

	return (fd)
end


# FNTOPN -- Open and initialize the template descriptor.

pointer procedure fntopn (template)

char	template[ARB]

pointer	pp
int	nchars
int	strlen()
errchk	calloc, malloc

begin
	nchars = strlen (template)

	call calloc (pp, LEN_FNTUHDR, TY_STRUCT)
	call malloc (U_TEMPLATE(pp), nchars, TY_CHAR)

	call strcpy (template, Memc[U_TEMPLATE(pp)], nchars)
	U_TEMPLATE_INDEX(pp) = U_TEMPLATE(pp)
	U_MAGIC(pp) = FNTU_MAGIC

	return (pp)
end


# FNTCLS -- Close the template descriptor, return space.

procedure fntcls (pp)

pointer	pp
errchk	syserr

begin
	if (pp == NULL || U_MAGIC(pp) != FNTU_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	if (U_FILDES(pp) != NULL)
	    call close (U_FILDES(pp))

	call mfree (U_TEMPLATE(pp), TY_CHAR)
	call mfree (pp, TY_STRUCT)
end
